#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Faraday Penetration Test IDE
Copyright (C) 2016  Infobyte LLC (http://www.infobytesec.com/)
See the file 'doc/LICENSE' for the license information

'''

from plugins import core
import re
import os
import socket
import json

__author__ = "Joaquin L. Pereyra | Federico Fernandez"
__copyright__ = "Copyright (c) 2016, Infobyte LLC"
__credits__ = ["Joaquin L. Pereyra"]
__license__ = ""
__version__ = "0.0.1"
__maintainer__ = "Joaquin L. Pereyra"
__email__ = "joaquinlp@infobytesec.com"
__status__ = "Development"


class WPScanPlugin(core.PluginBase):
    """ Handle the WPScan tool. Detects the output of the tool
    and adds the information to Faraday.
    """

    def __init__(self):
        """Initalizes the plugin with some basic params.
        Right now the plugin doesnt support being executed from another folder,
        like /dir/wpscan.rb
        """
        core.PluginBase.__init__(self)
        self.id = "wpscan"
        self.name = "WPscan"
        self.plugin_version = "0.2"
        self.version = "3.4.5"
        self._command_regex = re.compile(
                r"^((sudo )?(ruby )?(\.\/)?(wpscan)(.rb)?)")
        self.wpPath = self.get_wpscan_filepath()
        self.addSetting("WPscan path", str, self.wpPath)
        self.themes = {}
        self.plugins = {}
        self.wpversion = ''
        self.risks = {'AUTHBYPASS': 'high',
                        'BYPASS': 'med',
                        'CSRF': 'med',
                        'DOS': 'med',
                        'FPD': 'info',
                        'LFI': 'high',
                        'MULTI': 'unclassified',
                        'OBJECTINJECTION': 'med',
                        'PRIVESC': 'high',
                        'RCE': 'critical',
                        'REDIRECT': 'low',
                        'RFI': 'critical',
                        'SQLI': 'high',
                        'SSRF': 'med',
                        'UNKNOWN': 'unclassified',
                        'UPLOAD': 'critical',
                        'XSS': 'high',
                        'XXE': 'high'}

    def get_wpscan_filepath(self):
        home = os.path.expanduser("~")

        wpscan_path = os.path.join(home, '.wpscan')
        if os.path.exists(wpscan_path):
            return wpscan_path
        else:
            return None

    def search_file_in_wpscan_folder(self, wp_file):
        db_path = os.path.join(self.wpPath, 'db', wp_file)
        data_path = os.path.join(self.wpPath, 'data', wp_file)
        if os.path.exists(db_path):
            return db_path
        elif os.path.exists(data_path):
            return data_path

    def getPort(self, host, proto):
        p = re.search(r"\:([0-9]+)\/", host)
        if p is not None:
            return p.group(1)
        elif proto == 'https':
            return 443
        else:
            return 80

    def parseOutputWpscan(self, output):
        sp = output.split('0m Name:')  # cut by name
        for e in sp:
            if 'Title:' in e:
                if 'WordPress version' in e:
                    r = re.search(r'WordPress version (\d.\w)', e)  # get wordpress version
                    self.wpversion = r.group(1)

                elif 'wp-content/themes/' in e:
                    name = re.findall(r"Location: .+themes\/(.+)\/", e)  # get theme name
                    title = re.findall(r"Title: (.+)", e)  # get vulnerabilities title
                    self.themes[name[0]] = title  # insert theme in dicc {'themeName' : ['titles', 'titles']}

                else:
                    name = re.findall(r"Location: .+plugins\/(.+)\/", e)  # get plugin name
                    title = re.findall(r"Title: (.+)", e)  # get vulnerabilities title
                    self.plugins[name[0]] = title  # insert plugin in dicc {'plugin' : ['titles', 'titles']}

    def addThemesOrPluginsVulns(self, wpscan_db_filename, dic, host_id, serv_id, domain, wp_url, name):
        db_file_path = self.search_file_in_wpscan_folder(wpscan_db_filename)
        with open(db_file_path, "r") as data:
            j = json.load(data)
            for p in dic:
                for title in dic[p]:
                    for vuln in j[p]['vulnerabilities']:  # iter vulnerabilities
                        if vuln['title'] == title:  # if output title is equal
                            title = vuln['title']  # title
                            risk = self.risks[vuln['vuln_type']]  # vuln type (xss,rce,lfi,etc) - risk
                            location = wp_url+'wp-content/'+name+'/'+p+'/'
                            if vuln['references'].has_key('url') == True:  # if references
                                refs = vuln['references']['url'] #references[]
                            else:
                                refs = []  # references null
                            self.createAndAddVulnWebToService(
                                host_id,
                                serv_id,
                                title,
                                severity=risk,
                                website=domain,
                                ref=refs,
                                path=location)

    def addWPVulns(self, wpscan_db_filename, version, host_id, serv_id, domain):
        db_file_path = self.search_file_in_wpscan_folder(wpscan_db_filename)
        with open(db_file_path, "r") as data:
            j = json.load(data)
            for vuln in j[version]['vulnerabilities']: # iter vulnerabilities
                title = vuln['title']  # title
                risk = self.risks[vuln['vuln_type']]  # vuln type (xss,rce,lfi,etc) - risk
                if vuln['references'].has_key('url') == True:  # if references
                    refs = vuln['references']['url']  # references[]
                else:
                    refs = []  # references null
                self.createAndAddVulnWebToService(
                    host_id,
                    serv_id,
                    title,
                    severity=risk,
                    website=domain,
                    ref=refs)

    def parseOutputString(self, output, debug=False):
        """Parses the output given as a string by the wpscan tool and creates
        the appropiate hosts, service and vulnerabilites. Return
        nothing.
        """
        self.parseOutputWpscan(output)
        wp_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output)
        service, base_url = self.__get_service_and_url_from_output(output)
        if service and base_url:
            port = self.getPort(wp_url.group(1), service)
            host_ip = socket.gethostbyname_ex(base_url)[2][0]
            host_id = self.createAndAddHost(
                                            host_ip,
                                            hostnames=[base_url])

            service_id = self.createAndAddServiceToHost(host_id,
                                                        service,
                                                        "tcp",
                                                        ports=[port])

            potential_vulns = re.findall(r"(\[\!\].*)", output)
            for potential_vuln in potential_vulns:
                vuln_name, severity = self.__get_name_and_severity(potential_vuln)
                if vuln_name is not None:
                    vuln = potential_vuln  # they grow up so fast
                    path = self.__get_path_from_vuln(vuln)
                    self.createAndAddVulnWebToService(host_id, service_id,
                                                    name=vuln_name,
                                                    website=base_url,
                                                    path=path, severity=severity)

            if len(self.plugins) > 0:
                self.addThemesOrPluginsVulns(
                    'plugins.json',
                    self.plugins,
                    host_id,
                    service_id,
                    base_url,
                    wp_url.group(1),
                    'plugins')

            if len(self.wpversion) > 0:
                self.addWPVulns(
                    'wordpresses.json',
                    self.wpversion,
                    host_id,
                    service_id,
                    base_url)

            if len(self.themes) > 0:
                self.addThemesOrPluginsVulns(
                    'themes.json',
                    self.themes,
                    host_id,
                    service_id,
                    base_url,
                    wp_url.group(1),
                    'themes')

    def __get_service_and_url_from_output(self, output):
        """ Return the service (http or https) and the base URL (URL without
        protocol) from a given string. In case more than one URL is found,
        return the service and base_url of the first one, ignore others.
        """
        search_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output)
        if not search_url:
            return None, None
        else:
            service, base_url = search_url.group(2), search_url.group(3)
            return service, base_url

    def __get_name_and_severity(self, potential_vuln):
        """Regex the potential_vuln string against a regex with all
        the vulnerabilities given by WPscan. Returns a regex match object with
        the vulnerability's name and severity if the regex found something
        and (None, None) if the regex found nothing.
        """
        critical_search = re.search(r"Website is not fully configured|"
                                    "Debug log file found|"
                                    "wp-config\.php backup file has been found|"
                                    "searchreplacedb2.php has been found",
                                    potential_vuln)
        if critical_search:
            return critical_search.group(0), "critical"

        info_search = re.search(r"Directory listing is enabled|"
                                "An error_log file has been found|"
                                "file exists exposing a version number|"
                                "Full Path Disclosure|"
                                "Registration is enabled|"
                                "(Upload|Includes) directory has directory listing enabled|"
                                "Default first Wordpress username 'admin' is still used",
                                potential_vuln)
        if info_search:
            return info_search.group(0), "info"

        return None, None

    def __get_path_from_vuln(self, vuln):
        """Given a vuln as string, return the path as a string (empty string
        for path not found).
        """
        path_search = re.search("(?P<url>https?://[^\s]+)", vuln)
        path = path_search.group('url') if path_search else ""
        return path

    def processCommandString(self, username, current_path, command_string):
        return None


def createPlugin():
    return WPScanPlugin()
